CSS 的绝对单位、相对单位、最小单位
移动端适配的几大问题:
绝对单位问题,10px 的线在不同 dpr 屏幕上的视觉效果一致。
相对单位问题,元素的大小随屏幕的宽高缩放。
最小单位问题,1px的线。
#
绝对单位与相对单位问题#
产生原因dpr(设备像素比) = 物理像素(设备像素) / 设备独立像素(css像素)
普通屏幕的 dpr=1,高清屏幕的 dpr>=2。我们在样式代码上定义的元素大小是 CSS 像素,这意味着在普通屏和高清屏上展现的像素会有不同,同时也伴随这图片高清问题。例如,width: 2px; height: 2px
在普通屏上物理像素是 2X2=4,在高清屏上则是(2X2)X(2X2)=16。
从设计稿得到的尺寸通常是以 px 为单位的,如果要换算成 rem 作单位需要依赖编译器插件,而且会出现小数的情况,一般的做法是:
自定义 flexible.js 文件,将计算 font-size 标准值的 n 定为 3.75,这样可以使得在高清屏中 1rem = 100px。
在 Webpack 里使用 px2rem-loader。
#
flexible 1.0原理:根据不同设备的 dpr 值和宽度, 为根元素 html 动态设置 font-size,其他元素使用 rem(相对于根元素的 font-size 标准值的大小)做单位,并且监听 resize 事件重新计算根元素的 font-size。
根元素 font-size = document.documentElement.clientWidth * dpr / n
#
设备宽高与 window 相关的宽高
window.innerWidth
获得的是可视区域的宽,包含了纵向滚动条的宽度(IE8以及低版本浏览器不支持)。window.outerWidth
获得的是整个浏览器窗口的宽,包括侧边栏(如果存在)、窗口镶边(window chrome)和调正窗口大小的边框。包含调试窗及浏览器边框window.screen.width
获得的是整个屏幕的宽。window.screen.availWidth
获得的是浏览器窗口可使用的宽。与 client 相关的宽高
指的是元素的可视部分宽度和高度,即 padding + content 如果没有滚动条,即为元素设定的高度和宽度。 如果出现滚动条,滚动条会遮盖元素的宽高,那么该属性就是其本来的宽高减去滚动条的宽高。
document.documentElement.clientWidth
获得的是屏幕可视区域的宽,不包括滚动条与工具条。document.body.clientWidth
获得的也是可视区域的宽度,但是document.body.clientHeight
获得的是body内容的高度,如果内容只有 200px,那么这个高度也是200px,如果想通过它得到屏幕可视区域的宽高,需要样式设置.与 offset 相关的宽高
指的是元素的 border + padding + content的宽度和高度。 该属性和其内部的内容是否超出元素大小无关,只和本来设定的 border 以及 width 和 height 有关。
与 scroll 相关的宽高
#
flexible 2.0https://github.com/amfe/lib-flexible
由于viewport单位得到众多浏览器的兼容,vw、vh、vmin、vmax,100vw 表示布局视口的宽度,100vh 表示布局视口的高度,与 计算 html 的 font-size 标准值 rem 有异曲同工之妙,但写法更简洁。
浏览器支持情况如下:can i use
#
最小单位问题#
产生原因对于高清屏幕来说,设置CSS像素1px大小,其实横跨的是dpr个设备像素,这样看起来线条不够细,与设计稿产生出入。当我们需要在屏幕上实现1px时,实际上写的应该是0.5px / dpr,但是目前只有 iOS 8+
系统支持,安卓系统不支持。
因此,检测当前设备是否支持0.5px,如果不支持那么测试元素的高度会小于1px;如果支持则添加hairlines
类样式。
#
解决方案使用 border-image
但是 border 颜色变了就得重新制作图片,而且圆角会比较模糊。
使用 viewport 的 scale 缩放
适用于新项目,老项目的维护改动较大。
使用伪元素和 transform
伪元素设置绝对定位,并且和父元素左上角对其。将伪元素的长和宽先放大2倍,然后再设置一个边框,以左上角为中心,缩放到原来的0.5倍
优点:全机型兼容,实现了真正的1px,而且可以圆角。
缺点:暂用了伪元素,可能影响清除浮动。
#
刘海屏市面上的刘海屏和全面屏方案五花八门,如小米开发者文档提供的图所示:
![safearea](/img/blog/2020-05-17-CSS 的绝对单位、相对单位、最小单位/safearea.png)
上述两种屏幕都可以统称为刘海屏,不过对于右侧较小的刘海,业界一般称为水滴屏或美人尖。为便于说明,一般提到的「刘海屏」「刘海区」都同时指代上图两种屏幕。
当开发者在谈刘海屏适配问题时,主要是在谈论:如何避免内容被刘海和下巴遮挡。
通用的处理逻辑是:
判断设备的厂商和型号,是否是刘海屏。
如果设备是刘海屏,通过接口获取刘海区的宽高(刘海的左上角和右下角的坐标)。
对于安卓设备:
Android O ,各家厂商有自己的实现方案。
华为、小米、oppo,这三家都给了完整的解决方案。
Android P(9.0)开始,官方提供了适配异形屏的方式 Support display cutouts。
通过全新的 DisplayCutout 类,可以确定非功能区域的位置和形状,这些区域不应显示内容。 使用 getDisplayCutout() 函数可以确定这些凹口屏幕区域是否存在及其位置。
窗口布局属性 layoutInDisplayCutoutMode 控制内容如何呈现在刘海区域中:
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 这是默认行为,在竖屏模式下,内容会呈现到刘海区域中;但在横屏模式下,内容会显示黑边。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 在竖屏模式和横屏模式下,内容都会呈现到刘海区域中。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 内容从不呈现到刘海区域中。
对于苹果设备:
iOS 7 之后苹果给 UIViewController 引入了 topLayoutGuide 和 bottomLayoutGuide 两个属性来描述不希望被透明的状态栏或者导航栏遮挡的最高位置(status bar, navigation bar, toolbar, tab bar 等)。这个属性的值是一个 length 属性( topLayoutGuide.length),这个值可能由当前的 ViewController 或者 NavigationController 或者 TabbarController 决定。
iOS 11 开始弃用了这两个属性, 并且引入了 Safe Area 这个概念,官方建议: 不要把 Control 放在 Safe Area 之外的地方。
总的来说, safe area 可以看作是系统在所有的 view 上加了一个虚拟的 view, 这个虚拟的 view 的大小等都是跟 view 的位置等有关的(在 iPhoneX上才有值)。 在写代码的时候,自定义的控件都尽量针对 safe area 这个虚拟的 view 进行布局。
要考虑兼容“有 native header 的页面和无 native header 的页面相互跳转”的场景, 当有native header时,safe area 值为 0。
参考: